// Copyright Epic Games, Inc. All Rights Reserved.

#include "TDSCharacter.h"
#include "UObject/ConstructorHelpers.h"
#include "Camera/CameraComponent.h"
#include "Components/DecalComponent.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/SpringArmComponent.h"
#include "Materials/Material.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "Game/TDSGameInstance.h"
#include "TypeUtility.h"

ATDSCharacter::ATDSCharacter()
{
    // Set size for player capsule
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

    // Don't rotate character to camera direction
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // Configure character movement
    GetCharacterMovement()->bOrientRotationToMovement = true; // Rotate character to moving direction
    GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f);
    GetCharacterMovement()->bConstrainToPlane = true;
    GetCharacterMovement()->bSnapToPlaneAtStart = true;

    // Create a camera boom...
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
    CameraBoom->SetUsingAbsoluteRotation(true); // Don't want arm to rotate when character does
    CameraBoom->TargetArmLength = 800.f;
    CameraBoom->SetRelativeRotation(FRotator(-60.f, 0.f, 0.f));
    CameraBoom->bDoCollisionTest = false; // Don't want to pull camera in when it collides with level

    // Create a camera...
    TopDownCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera"));
    TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
    TopDownCameraComponent->bUsePawnControlRotation = false; // Camera does not rotate relative to arm

    // Activate ticking in order to update the cursor every frame.
    PrimaryActorTick.bCanEverTick = true;
    PrimaryActorTick.bStartWithTickEnabled = true;
}

void ATDSCharacter::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    if (CurrentCursor)
    {

        if (APlayerController* PC = Cast<APlayerController>(GetController()))
        {
            FHitResult TraceHitResult;
            PC->GetHitResultUnderCursor(ECC_Visibility, true, TraceHitResult);
            FVector CursorFV = TraceHitResult.ImpactNormal;
            FRotator CursorR = CursorFV.Rotation();
            CurrentCursor->SetWorldLocation(TraceHitResult.Location);
            CurrentCursor->SetWorldRotation(CursorR);
        }
    }

    MovementTick(DeltaSeconds);
    ZoomingTick(DeltaSeconds);
}

void ATDSCharacter::BeginPlay()
{
    Super::BeginPlay();

    if (CursorMaterial)
    {
        CurrentCursor = UGameplayStatics::SpawnDecalAtLocation(GetWorld(), CursorMaterial, CursorSize, FVector(0.0f));
    }

    InitWeapon(InitWeaponName);
}

void ATDSCharacter::SetupPlayerInputComponent(UInputComponent* NewInputComponent)
{
    Super::SetupPlayerInputComponent(NewInputComponent);
    NewInputComponent->BindAxis(TEXT("MoveForward"), this, &ATDSCharacter::InputAxisX);
    NewInputComponent->BindAxis(TEXT("MoveRight"), this, &ATDSCharacter::InputAxisY);

    // Zomming
    NewInputComponent->BindAction(TEXT("ZoomIn"), IE_Pressed, this, &ATDSCharacter::ZoomIn);
    NewInputComponent->BindAction(TEXT("ZoomOut"), IE_Pressed, this, &ATDSCharacter::ZoomOut);

    // Fire Bindings
    NewInputComponent->BindAction(TEXT("FireEvent"), IE_Pressed, this, &ATDSCharacter::InputAttackPressed);
    NewInputComponent->BindAction(TEXT("FireEvent"), IE_Released, this, &ATDSCharacter::InputAttackReleased);

    NewInputComponent->BindAction(TEXT("ReloadEvent"), IE_Pressed, this, &ATDSCharacter::TryReloadWeapon);
}

void ATDSCharacter::InputAxisX(float Value)
{
    AxisX = Value;
}

void ATDSCharacter::InputAxisY(float Value)
{
    AxisY = Value;
}

void ATDSCharacter::MovementTick(float DeltaTime)
{
    AddMovementInput(FVector(1.0f, 0.0f, 0.0f), AxisX);
    AddMovementInput(FVector(0.0f, 1.0f, 0.0f), AxisY);

    APlayerController* myController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
    if (myController)
    {
        FVector WorldLocation;
        FVector WordlDirection;
        myController->DeprojectMousePositionToWorld(WorldLocation, WordlDirection);
        FVector CurDirection = (WordlDirection * 500000.0f) + WordlDirection;
        FVector PlaneNormal(0.0f, 0.0f, 1.0f);
        FVector Intersection;
        float T = -1.0f;
        UKismetMathLibrary::LinePlaneIntersection_OriginNormal(
            WorldLocation, CurDirection, GetActorLocation(), PlaneNormal, T, Intersection);
        float FindRotaterResultYaw = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), Intersection).Yaw;
        SetActorRotation(FQuat(FRotator(0.0f, FindRotaterResultYaw, 0.0f)));
    }
}

void ATDSCharacter::ZoomingTick(float DeltaTime)
{
    CameraBoom->TargetArmLength = UKismetMathLibrary::FInterpTo(CameraBoom->TargetArmLength, TargHight, DeltaTime, 3.0f);
}

void ATDSCharacter::ZoomIn()
{
    if (CameraBoom->TargetArmLength >= MinHight)
    {
        TargHight = CameraBoom->TargetArmLength - ZoomStep;
    }
}

void ATDSCharacter::ZoomOut()
{
    if (CameraBoom->TargetArmLength <= MaxHight)
    {
        TargHight = CameraBoom->TargetArmLength + ZoomStep;
    }
}

void ATDSCharacter::CharacterUpdate()
{
    float ResSpeed = 600.0f;
    switch (MovementState)
    {
    case EMovementState::Aim_State:
        ResSpeed = MovementInfo.AimSpeed;
        break;
    case EMovementState::AimWalk_State:
        ResSpeed = MovementInfo.AimWalkSpeed;
        break;
    case EMovementState::Walk_State:
        ResSpeed = MovementInfo.WalkSpeed;
        break;
    case EMovementState::Run_State:
        ResSpeed = MovementInfo.RunSpeed;
        break;
    case EMovementState::Sprint_State:
        ResSpeed = MovementInfo.SprintSpeed;
        break;
    default:
        break;
    }

    GetCharacterMovement()->MaxWalkSpeed = ResSpeed;
}

void ATDSCharacter::ChangeMovementState()
{
    if (!WalkEnabled && !SprintRunEnabled && !AimEnabled)
    {
        MovementState = EMovementState::Run_State;
    }
    else
    {
        if (SprintRunEnabled)
        {
            WalkEnabled = false;
            AimEnabled = false;
            MovementState = EMovementState::Sprint_State;
        }
        if (WalkEnabled && !SprintRunEnabled && AimEnabled)
        {
            MovementState = EMovementState::AimWalk_State;
        }
        else
        {
            if (WalkEnabled && !SprintRunEnabled && !AimEnabled)
            {
                MovementState = EMovementState::Walk_State;
            }
            else
            {
                if (!WalkEnabled && !SprintRunEnabled && AimEnabled)
                {
                    MovementState = EMovementState::Aim_State;
                }
            }
        }
    }
    CharacterUpdate();

    // Weapon State update
    if (CurrentWeapon)
    {
        CurrentWeapon;
    }
}

UDecalComponent* ATDSCharacter::GetCursorToWorld()
{
    return CurrentCursor;
}

void ATDSCharacter::InitWeapon(FName IdWeapon)
{

    UTDSGameInstance* myGI = Cast<UTDSGameInstance>(GetGameInstance());
    FWeaponInfo myWeaponInfo;
    if (myGI)
    {
        if (myGI->GetWeaponInfoByName(IdWeapon, myWeaponInfo))
        {
            if (myWeaponInfo.WeaponClass)
            {
                FVector SpawnLocation = FVector(0);
                FRotator SpawnRotation = FRotator(0);

                FActorSpawnParameters SpawnParams;
                SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
                SpawnParams.Owner = GetOwner();
                SpawnParams.Instigator = GetInstigator();

                AWeaponDefault* myWeapon =
                    Cast<AWeaponDefault>(GetWorld()->SpawnActor(myWeaponInfo.WeaponClass, &SpawnLocation, &SpawnRotation, SpawnParams));
                if (myWeapon)
                {
                    FAttachmentTransformRules Rule(EAttachmentRule::SnapToTarget, false);
                    myWeapon->AttachToComponent(GetMesh(), Rule, FName("WeaponSocketRightHand"));
                    CurrentWeapon = myWeapon;

                    myWeapon->WeaponSetting = myWeaponInfo;
                    myWeapon->AddicionalInfo.Round = myWeaponInfo.MaxRound;
                    myWeapon->UpdateStateWeapon(MovementState);
                }
            }
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("ATDSCharacter::InitWeapon - Not Found Weapon Row in Table"));
        }
    }
}

void ATDSCharacter::TryReloadWeapon() 
{
    if (CurrentWeapon)
    {
        if (CurrentWeapon->GetWeaponRound() < CurrentWeapon->GetMaxRound())
        {
            CurrentWeapon->InitReaload();
        }
    }
}

AWeaponDefault* ATDSCharacter::GetCurrentWeapon()
{
    return CurrentWeapon;
}

void ATDSCharacter::AttackCharEvent(bool bIsFiring)
{
    if (CurrentWeapon)
    {
        // ToDo Check Meele or range
        CurrentWeapon->SetWeaponStateFire(bIsFiring);
        UE_LOG(LogTemp, Warning, TEXT("ATDSCharacter::AttackCharEvent -  CurrentWeapon is  valid"));
    }
    else
        UE_LOG(LogTemp, Warning, TEXT("ATDSCharacter::AttackCharEvent -  CurrentWeapon - NULL"));
}

void ATDSCharacter::InputAttackPressed()
{
    AttackCharEvent(true);
    UE_LOG(LogTemp, Warning, TEXT("ATDSCharacter::InputAttackPressed() - StartFire"));
}

void ATDSCharacter::InputAttackReleased()
{
    AttackCharEvent(false);
    UE_LOG(LogTemp, Warning, TEXT("ATDSCharacter::InputAttackReleased() - StartFire"));
}
